local proto_manager = require "common.proto_manager"
local skynet = require "skynet"
local table_utils = require "utils.table_utils"

local gFightMaxTime = 60 * 3 * 100
local gSettlementMaxTime = 5 * 100

local match_room = {}

match_room.Stage = {
    Mtching = 1,  -- 匹配
    Prepare = 2,  -- 准备
    Ready   = 3, -- 已准备
    Started = 4,  -- 开始
    Settlement = 5, --结算
    End = 6, -- 结束
}

function match_room:new(match_id)
    local o = {
        match_id = match_id,
        players = {},
        stage = match_room.Stage.Mtching,
        onNotifyMatchInfo = nil,
        onNotifyGameStart = nil,
        gameStartInfos = {},
        endGameInfos = {},
        onNotifyGameEnd = nil,
        onNotifyGameEndFailed = nil,
        fightTimer = nil,
        settlementTimer = nil,
    }
    setmetatable(o,self)
    self.__index = self
    return o
end

function match_room:getID()
    return self.match_id
end

function match_room:getStage()
    return self.state
end

function match_room:getGameStartInfo(open_id)
    return self.gameStartInfos[open_id] or nil
end

function match_room:isInPrepare()
    return match_room.Stage.Prepare == self.stage
end

function match_room:isStarted()
    return match_room.Stage.Started == self.stage
end

function match_room:canLeave()
    return match_room.Stage.Mtching == self.stage
            or match_room.Stage.Prepare == self.stage
end

function match_room:canEnd()
    return match_room.Stage.Started == self.stage
            or match_room.Stage.Settlement == self.stage
end

function match_room:setOnline(open_id)
    local info = self:getPlayerInfo(open_id)
    if not info then
        return false
    end
    info.isOffline = false
    return true
end

function match_room:setOffline(open_id)
    local info = self:getPlayerInfo(open_id)
    if not info then
        return false
    end
    info.isOffline = true
    return true
end

function match_room:isEmpty()
    return 0 == #self.players
end

function match_room:getPlayerInfo(open_id)
    for i,v in ipairs(self.players) do
        if open_id == v.open_id then
            return v
        end
    end
    return nil
end

function match_room:getPlayers()
    return self.players
end

local function toMatchPlayerInfo(player)
    return {
        open_id = player.open_id,
        index = player.index,
        state = player.state,
    }
end

function match_room:getPlayerInfos()
    local infos = {}
    for _,v in ipairs(self.players) do
        table.insert(infos,toMatchPlayerInfo(v))
    end
    return infos
end

function match_room:foreachPlayers(func)
    for i,v in ipairs(self.players) do
        func(v)
    end
end

function match_room:setPlayerReady(open_id,state)

    if match_room.Stage.Prepare ~= self.stage then
        return false
    end

    local info = self:getPlayerInfo(open_id)
    if not info then
        return false
    end

    if state ~= 0 then
        info.state = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_Ready")
    else
        info.state = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_None")
    end

    return true

end

local MatchPlayerState_Ready = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_Ready")

function match_room:checkAllReady()

    local ok = true
    for i,v in ipairs(self.players) do
        if MatchPlayerState_Ready ~= v.state then
            ok = false
            break
        end
    end

    if ok and match_room.Stage.Prepare == self.stage then
        self.stage = match_room.Stage.Ready
    end

    return ok
end

function match_room:addPlayer(playerInfo)

    if match_room.Stage.Mtching ~= self.stage then
        return false
    end

    if self:getPlayerInfo(playerInfo.open_id) then
        return false
    end

    local info = {
        open_id = playerInfo.open_id,
        index = 1,
        state = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_None"),
        isOffline = false,
    }

    if #self.players > 0 then

        info.index = (self.players[1].index + 1) % 3
        if 0 == info.index then
            info.index = 1
        end
        table.insert(self.players,info)

        self.stage = match_room.Stage.Prepare

        if self.onNotifyMatchInfo then
            self.onNotifyMatchInfo(self,self.players[1].open_id,{ toMatchPlayerInfo(info) })
        end

    else

        table.insert(self.players,info)

    end

    return true

end

function match_room:removePlayer(open_id)

    if not self:canLeave() then
        return false
    end

    for i,v in ipairs(self.players) do
        if open_id == v.open_id then

            table.remove(self.players,i)

            if #self.players > 0 then

                self.stage = match_room.Stage.Mtching

                self.players[1].state = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_None")

                if self.onNotifyMatchInfo then
                    local info = {
                        open_id = open_id,
                        index = v.index,
                        state = proto_manager.enum_id("MatchPlayerState","MatchPlayerState_Leave"),
                    }
                    if self.onNotifyMatchInfo then
                        self.onNotifyMatchInfo(self,self.players[1].open_id,
                                {toMatchPlayerInfo(info),toMatchPlayerInfo(self.players[1])})
                    end
                end
            end

            return true

        end
    end

    return false

end

function match_room:onGameStart(ip,port,players)

    if match_room.Stage.Ready ~= self.stage then
        return false
    end

    self.stage = match_room.Stage.Started

    local tempPlayes = {}
    for _,p in ipairs(players) do
        local player = {
            open_id = p.open_id,
            index = p.index
        }
        table.insert(tempPlayes,player)
    end

    local random_seed = math.random(1,100000000)

    for _,p in ipairs(players) do
        self.gameStartInfos[p.open_id] = {
            room_id = self:getID(),
            random_seed = random_seed,
            players = tempPlayes,
            server_info = {
                ip = ip,
                port = port,
            },
            token = p.token,
        }
    end

    self.fightTimer = skynet.timeout(gFightMaxTime,function()
        self:onFightTimeout()
    end)

    if self.onNotifyGameStart then
        self.onNotifyGameStart(self)
    end

    return true

end

function match_room:onFightTimeout()

    self:_stopAllTimer()

    self.stage = match_room.Stage.End

    if self.onNotifyGameEndFailed then
        self.onNotifyGameEndFailed(self,proto_manager.errcode("ErrorCode_RaceFightTimeout"))
    end

end

function match_room:onSettlementTimeout()

    self:_stopAllTimer()

    self.stage = match_room.Stage.End

    self:_checkEnd(true)

end

function match_room:_checkEnd(force)

    local endGameInfoCount = table_utils.getNum(self.endGameInfos)
    if (endGameInfoCount >= #self.players)
            or (force and endGameInfoCount > (#self.players / 2)) then

        self:_endProc()

    else
        if force then

            self:_stopAllTimer()

            self.stage = match_room.Stage.End

            if self.onNotifyGameEndFailed then
                self.onNotifyGameEndFailed(self,proto_manager.errcode("ErrorCode_RaceSettlementPlayerNotEnough"))
            end
        end
    end

end

function match_room:_stopAllTimer()

    if self.fightTimer ~= nil then
        skynet.killthread(self.fightTimer)
        self.fightTimer = nil
    end

    if self.settlementTimer ~= nil then
        skynet.killthread(self.settlementTimer)
        self.settlementTimer = nil
    end

end

local function checkEndGameInfo(infos,srcPlayers)

    local function cmp(info1,info2)
        for _,v1 in ipairs(info1.players) do
            local ok = false
            for _,v2 in ipairs(info2.players) do
                if v1.open_id == v2.open_id then
                    if v1.score == v2.score then
                        ok = true
                    end
                    break
                end
            end
            if not ok then
                return false
            end
        end
        return true
    end

    for i=1,#srcPlayers do
        local info1 = infos[srcPlayers[i].open_id]
        for j=i+1,#srcPlayers do
            local info2 = infos[srcPlayers[j].open_id]
            if not cmp(info1,info2) then
                return false
            end
        end
    end

    local info = {
        players = infos[srcPlayers[1].open_id].players
    }

    return true,info

end

function match_room:_endProc()

    self:_stopAllTimer()

    self.stage = match_room.Stage.End

    local ok,info = checkEndGameInfo(self.endGameInfos,self.players)

    if not ok then
        if self.onNotifyGameEndFailed then
            self.onNotifyGameEndFailed(self,proto_manager.errcode("ErrorCode_RaceSettlementCheckFailed"))
        end
        return
    end

    if self.onNotifyGameEnd then
        self.onNotifyGameEnd(self,info)
    end

end

function match_room:onPlayerRequestEnd(open_id,info)

    if not self:canEnd() then
        return false
    end

    if not self:getPlayerInfo(open_id) then
        return false
    end

    self.endGameInfos[open_id] = info

    skynet.error("onPlayerRequestEnd:\n" .. table_utils.tostring(info))

    if match_room.Stage.Started == self.stage then

        if self.fightTimer ~= nil then
            skynet.killthread(self.fightTimer)
            self.fightTimer = nil
        end

        self.stage = match_room.Stage.Settlement

        self.settlementTimer = skynet.timeout(gSettlementMaxTime,function()
            self:onSettlementTimeout()
        end)

    else

        self:_checkEnd(false)

    end

    return true

end


return match_room